package com.demo.common.excel;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.demo.common.exceptions.ServiceException;
import com.demo.common.utils.JsonUtil;
import com.demo.common.vo.BasePage;
import com.demo.common.vo.PageResult;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * excel处理service
 */
@Service
@Slf4j
public class ExcelOperatorServiceImpl implements IExcelOperateService, InitializingBean, ApplicationContextAware {
    /**
     * 场景缓存
     */
    private Map<String, ExcelServiceWrapper> sceneMap = new HashMap<>();
    /**
     * 导入header缓存
     */
    private Map<String, List<FieldWrapper>> importHeader = new HashMap<>();
    /**
     * 导出header缓存
     */
    private Map<String, List<FieldWrapper>> exportHeader = new HashMap<>();
    /**
     * context,用来初始化上面信息的
     */
    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        //找到所有有ExcelService注解的bean
        String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(ExcelService.class);
        for (String beanName : beanNamesForAnnotation) {
            Object bean = applicationContext.getBean(beanName);
            Class<?> userClass = ClassUtils.getUserClass(bean);
            ExcelService excelService = userClass.getAnnotation(ExcelService.class);
            ExcelServiceWrapper excelServiceWrapper = new ExcelServiceWrapper();
            excelServiceWrapper.setTargetClass(excelService.targetClass());
            excelServiceWrapper.setTargetBean(bean);
            Method exportMethod = Arrays.stream(userClass.getMethods()).filter(it -> it.getName().equals(excelService.exportMethod()) && it.getParameterCount() == 1).findAny().orElse(null);
            if (exportMethod == null) {
                log.warn("ExcelService场景:{}导出方法未找到或者方法参数数量不为1", excelService.scene());
            } else {
                excelServiceWrapper.setExportParamClass(exportMethod.getParameterTypes()[0]);
                excelServiceWrapper.setExportMethod(exportMethod);
                Class<?> returnType = exportMethod.getReturnType();
                int exportReturnType=-1;
                if(returnType.equals(List.class)){
                    exportReturnType=1;
                }else if(returnType.equals(Page.class)){
                    exportReturnType=2;
                }else if(returnType.equals(PageResult.class)){
                    exportReturnType=3;
                }else {
                    log.warn("ExcelService场景:{}导出方法返回类型匹配失败，自动导出不可用", excelService.scene());
                }
                //如果返回的是分页数据，那么请求参数也必须带分页参数,这里限定必须是BasePage的子类
                if(exportReturnType==2||exportReturnType==3){
                    if(!BasePage.class.isAssignableFrom(excelServiceWrapper.getExportParamClass())){
                        exportReturnType=-1;
                        log.warn("ExcelService场景:{}导出方法返回为分页，但请求参数不带分页，自动导出不可用", excelService.scene());
                    }
                }
                excelServiceWrapper.setExportReturnType(exportReturnType);
            }
            Method importMethod = Arrays.stream(userClass.getMethods()).filter(it -> it.getName().equals(excelService.importMethod()) && it.getParameterCount() == 1).findAny().orElse(null);
            if (importMethod == null) {
                log.warn("ExcelService场景:{}导入方法未找到或者方法参数数量不为1", excelService.scene());
            } else {
                Class<?> parameterType = importMethod.getParameterTypes()[0];
                if (parameterType.equals(List.class)) {
                    excelServiceWrapper.setImportMethodList(true);
                }
                excelServiceWrapper.setImportMethod(importMethod);
            }
            //有相同的场景，warn,覆盖
            ExcelServiceWrapper old = sceneMap.get(excelService.scene());
            if (old != null) {
                log.warn("ExcelService scene:{} is already exist,override it,old ExcelService is {}", excelService.scene(), old);
            }
            sceneMap.put(excelService.scene(), excelServiceWrapper);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    @SneakyThrows
    public void createTemplate(String scene, HttpServletResponse response) {
        getExcelServiceWrapper(scene);
        if (!importHeader.containsKey(scene)) {
            initImportHeader(scene);
        }
        List<FieldWrapper> fieldWrappers = importHeader.get(scene);
        if (CollectionUtils.isEmpty(fieldWrappers)) {
            throw new ServiceException("场景" + scene + "未维护导入模板字段");
        }
        List<List<String>> headers = fieldWrappers.stream().map(it -> Collections.singletonList(it.getName())).collect(Collectors.toList());
        ExcelUtils.createTemplate(headers,response);
    }

    @Override
    @SneakyThrows
    public void importData(String scene, InputStream in) {
        ExcelServiceWrapper excelServiceWrapper = getExcelServiceWrapper(scene);
        if (!importHeader.containsKey(scene)) {
            initImportHeader(scene);
        }
        List<FieldWrapper> fieldWrappers = importHeader.get(scene);
        if (CollectionUtils.isEmpty(fieldWrappers)) {
            throw new ServiceException("场景" + scene + "未维护导入模板字段");
        }
        //读取数据
        List data = ExcelUtils.readImportData(fieldWrappers, in);
        //数据处理
        if (excelServiceWrapper.isImportMethodList()) {
            excelServiceWrapper.getImportMethod().invoke(excelServiceWrapper.getTargetBean(), data);
        } else {
            for (Object single : data) {
                excelServiceWrapper.getImportMethod().invoke(excelServiceWrapper.getTargetBean(), single);
            }
        }
    }

    @Override
    @SneakyThrows
    public void exportData(String scene, Map<String, Object> params, HttpServletResponse response) {
        ExcelServiceWrapper excelServiceWrapper =getExcelServiceWrapper(scene);
        int exportReturnType = excelServiceWrapper.getExportReturnType();
        if(excelServiceWrapper.getExportReturnType()==-1){
            throw new ServiceException("场景"+scene+"导出方法校验失败，导出不可用，请联系开发排查");
        }
        if (!exportHeader.containsKey(scene)) {
            initExportHeader(scene);
        }
        List<FieldWrapper> fieldWrappers = exportHeader.get(scene);
        if (CollectionUtils.isEmpty(fieldWrappers)) {
            throw new ServiceException("场景" + scene + "未维护导出模板字段");
        }
        Object param =JsonUtil.json2Object(JsonUtil.object2Json(params), excelServiceWrapper.getExportParamClass());
        response.setHeader("Content-disposition", "attachment;filename=export.xlsx");
        List<List<String>> headers = fieldWrappers.stream().map(it -> Collections.singletonList(it.getName())).collect(Collectors.toList());
        ExcelWriter excelWriter = EasyExcelFactory.write().file(response.getOutputStream()).head(headers).build();
        //不分页查一次直接写入
        if(exportReturnType==1){
            List records= (List) excelServiceWrapper.getExportMethod().invoke(excelServiceWrapper.getTargetBean(), param);
            List excelData = ExcelUtils.convert2ExcelData(fieldWrappers, records);
            excelWriter.write(excelData,new WriteSheet());
            excelWriter.finish();
            return;
        }
        long current=1;
        long size=1000;
        BasePage queryParam= (BasePage) param;
        queryParam.setCurrent(current);
        queryParam.setSize(size);
        while (true){
            //分页查询数据
            Object result=excelServiceWrapper.getExportMethod().invoke(excelServiceWrapper.getTargetBean(), param);
            List records=Collections.emptyList();
            if(exportReturnType==2){
                Page page= (Page) result;
                records=page.getRecords();
            }else if(exportReturnType==3){
                PageResult pageResult= (PageResult) result;
                records=pageResult.getRecords();
            }
            //有数据就写入
            if(!records.isEmpty()){
                List excelData = ExcelUtils.convert2ExcelData(fieldWrappers, records);
                excelWriter.write(excelData,new WriteSheet());
            }
            //说明到最后一页数据了
            if(records.size()<size){
                break;
            }
            queryParam.setCurrent(current++);
        }
        excelWriter.finish();
    }

    /**
     * 初始化导入header,缓存
     * @param scene
     */
    private void initImportHeader(String scene) {
        Class targetClass = getExcelServiceWrapper(scene).getTargetClass();
        List<Field> fieldsListWithAnnotation = FieldUtils.getFieldsListWithAnnotation(targetClass,ExcelField.class);
        List<FieldWrapper> wrapperList = new ArrayList<>(fieldsListWithAnnotation.size());
        for (Field field : fieldsListWithAnnotation) {
            field.setAccessible(true);
            ExcelField annotation = field.getAnnotation(ExcelField.class);
            if (annotation.useImport()) {
                FieldWrapper fieldWrapper = new FieldWrapper();
                fieldWrapper.setField(field);
                fieldWrapper.setSort(annotation.sort());
                if (StringUtils.isNotBlank(annotation.name())) {
                    fieldWrapper.setName(annotation.name());
                } else {
                    fieldWrapper.setName(field.getName());
                }
                if (StringUtils.isNotBlank(annotation.importConvert())) {
                    fieldWrapper.setImportConverter(annotation.importConvert());
                } else if (StringUtils.isNotBlank(annotation.simpleConvert())) {
                    String converterName=scene+"_"+field.getName();
                    ExcelUtils.registerSimpleConvert(converterName,annotation.simpleConvert());
                    fieldWrapper.setImportConverter(converterName+"_import");
                }
                wrapperList.add(fieldWrapper);
            }
        }
        List<FieldWrapper> fieldWrappers = wrapperList.stream().sorted(Comparator.comparing(FieldWrapper::getSort)).collect(Collectors.toList());
        importHeader.put(scene, fieldWrappers);
    }

    /**
     * 初始化导出header，缓存
     * @param scene
     */
    private void initExportHeader(String scene) {
        Class targetClass = getExcelServiceWrapper(scene).getTargetClass();
        List<Field> fieldsListWithAnnotation = FieldUtils.getFieldsListWithAnnotation(targetClass,ExcelField.class);
        List<FieldWrapper> wrapperList = new ArrayList<>(fieldsListWithAnnotation.size());
        for (Field field : fieldsListWithAnnotation) {
            field.setAccessible(true);
            ExcelField annotation = field.getAnnotation(ExcelField.class);
            if (annotation.useExport()) {
                FieldWrapper fieldWrapper = new FieldWrapper();
                fieldWrapper.setField(field);
                fieldWrapper.setSort(annotation.sort());
                if (StringUtils.isNotBlank(annotation.name())) {
                    fieldWrapper.setName(annotation.name());
                } else {
                    fieldWrapper.setName(field.getName());
                }
                if (StringUtils.isNotBlank(annotation.exportConvert())) {
                    fieldWrapper.setExportConverter(annotation.exportConvert());
                } else if (StringUtils.isNotBlank(annotation.simpleConvert())) {
                    String converterName=scene+"_"+field.getName();
                    ExcelUtils.registerSimpleConvert(converterName,annotation.simpleConvert());
                    fieldWrapper.setExportConverter(converterName+"_export");
                }
                wrapperList.add(fieldWrapper);
            }
        }
        List<FieldWrapper> fieldWrappers = wrapperList.stream().sorted(Comparator.comparing(FieldWrapper::getSort)).collect(Collectors.toList());
        exportHeader.put(scene, fieldWrappers);
    }

    private ExcelServiceWrapper getExcelServiceWrapper(String scene){
        ExcelServiceWrapper excelServiceWrapper = sceneMap.get(scene);
        if (excelServiceWrapper == null) {
            throw new ServiceException("场景" + scene + "不存在");
        }
        return excelServiceWrapper;
    }

}
